/*
 * Copyright:  2018 smarabbit studio.
 *
 * Licensed under the Confluent Community License; you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 * http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under
 *  the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 *  either express or implied.  See the License for the specific language governing
 *  permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:    2020/8/13 上午11:35
 *
 */

package com.massyframework.beanskin.graphql.servlet;

import static graphql.execution.FieldCollectorParameters.newParameters;

import java.util.concurrent.CompletableFuture;

import com.massyframework.beanskin.graphql.fetching.CustomizeField;
import com.massyframework.beanskin.graphql.fetching.MergedFieldFactory;
import com.massyframework.beanskin.graphql.util.GraphQLContextUtils;

import graphql.ExecutionResult;
import graphql.execution.AsyncExecutionStrategy;
import graphql.execution.DataFetcherExceptionHandler;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionStepInfo;
import graphql.execution.ExecutionStrategyParameters;
import graphql.execution.FieldCollectorParameters;
import graphql.execution.MergedField;
import graphql.execution.MergedSelectionSet;
import graphql.execution.NonNullableFieldValidator;
import graphql.execution.SimpleDataFetcherExceptionHandler;
import graphql.schema.GraphQLObjectType;



/**
 * {@link AsyncExecutionStrategyEx}, 扩展AsynExecutionStrategy，支持服务端替换返回前端的字段。
 */
public class AsyncExecutionStrategyEx extends AsyncExecutionStrategy {

    /**
     * The standard graphql execution strategy that runs fields asynchronously
     */
    public AsyncExecutionStrategyEx() {
       this(new SimpleDataFetcherExceptionHandler());
    }

    /**
     * Creates a execution strategy that uses the provided exception handler
     *
     * @param exceptionHandler the exception handler to use
     */
    public AsyncExecutionStrategyEx(DataFetcherExceptionHandler exceptionHandler) {
        super(exceptionHandler);
    }




    /**
     * Called to turn an java object value into an graphql object value
     *
     * @param executionContext   contains the top level execution parameters
     * @param parameters         contains the parameters holding the fields to be executed and source object
     * @param resolvedObjectType the resolved object type
     * @param result             the result to be coerced
     *
     * @return a promise to an {@link ExecutionResult}
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
	protected CompletableFuture<ExecutionResult> completeValueForObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLObjectType resolvedObjectType, Object result) {        
    	if (result != null){
            MergedFieldFactory factory =  null;
            if (parameters.getField().getSingleField() instanceof CustomizeField){
                factory = ((CustomizeField)parameters.getField().getSingleField()).getMergedFieldFactory();
            }else {
                String path = parameters.getPath().toString();
                factory = GraphQLContextUtils.removeAttribute(executionContext.getContext(), path, MergedFieldFactory.class);
            }

            if (factory != null){
                MergedField field = factory.createMargedField(executionContext, parameters,
                        result, resolvedObjectType);

                parameters = ExecutionStrategyParameters.newParameters(parameters)
                        .field(field)
                        .build();

            }
        }

        ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo();

        FieldCollectorParameters collectorParameters = newParameters()
                    .schema(executionContext.getGraphQLSchema())
                    .objectType(resolvedObjectType)
                    .fragments(executionContext.getFragmentsByName())
                    .variables(executionContext.getVariables())
                    .build();


        MergedSelectionSet subFields = fieldCollector.collectFields(collectorParameters, parameters.getField());

        ExecutionStepInfo newExecutionStepInfo = executionStepInfo.changeTypeWithPreservedNonNull(resolvedObjectType);
        NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, newExecutionStepInfo);

        ExecutionStrategyParameters newParameters = parameters.transform(builder ->
                builder.executionStepInfo(newExecutionStepInfo)
                        .fields(subFields)
                        .nonNullFieldValidator(nonNullableFieldValidator)
                        .source(result)
        );



        // Calling this from the executionContext to ensure we shift back from mutation strategy to the query strategy.

        return executionContext.getQueryStrategy().execute(executionContext, newParameters);
    }

}
